package com.googlecode.nls4j;

import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

/**
 * The main purpose of this class is to listen for locale changes in
 * {@link NlsContext} and keep reference to the bundle corresponding to the
 * current locale. <br/>
 * <br/>
 * If the implementation of this class is non-lazy (the method {@link #isLazy()}
 * returns false), an instance of this implementation registers itself in the
 * NlsContext and sets reference to the current NlsBundle during initialization.
 * The reference is updated each time the locale is changed (the method
 * {@link #handleLocaleChange(NlsContext, Locale, Locale, Object)
 * handleLocaleChange(NlsContext, Locale, Locale, Object)} is called by the
 * listening NlsContext).<br/>
 * Most probably, non-lazy implementations will implement the methods
 * {@link #getBundle()} and
 * {@link #handleLocaleChange(NlsContext, Locale, Locale, Object)
 * handleLocaleChange(NlsContext, Locale, Locale, Object)} in the similar way:
 * 
 * <pre>
 * public NlsBundle&lt;K, V&gt; getBundle() {
 *     return ref;
 * }
 * 
 * public void handleLocaleChange(final NlsContext context,
 *         final Locale oldLocale, final Locale newLocale, final Object handback) {
 *     ref = bundleStorage.getBundle(newLocale);
 * }
 * </pre>
 * 
 * Non-lazy implementations are best suitable for desktop applications, in which
 * locale is changing rarely. The recommended BundleStorage for such applications
 * is {@link KeyValuePairStorage}. <br/>
 * Non-lazy implementation usage example:
 * 
 * <pre>
 * public enum Msg implements KeyValuePair&lt;String&gt; {
 *     Hello(&quot;Hello&quot;),
 *     Bye(&quot;Bye&quot;);
 * 
 *     private static final CurrentBundle&lt;Msg, String&gt; currentBundle = CurrentBundle
 *             .newInstance(
 *                 NlsContext.getDefault(),
 *                 new KeyValuePairStorage&lt;Msg, String&gt;(
 *                         Msg.class, EnumBundleLoader
 *                                 .&lt;Msg, String&gt; getDefault(false), EnumSet
 *                                 .allOf(Msg.class)), false);
 *     private final String defaultValue;
 *     private volatile String value;
 * 
 *     private Msg(final String defaultValue) {
 *         this.defaultValue = value = defaultValue;
 *     }
 * 
 *     public static CurrentBundle&lt;Msg, String&gt; getCurrentBundle() {
 *         return currentBundle;
 *     }
 * 
 *     public String getName() {
 *         return name();
 *     }
 * 
 *     public String getDefaultValue(final Locale locale) {
 *         return defaultValue;
 *     }
 * 
 *     public String getValue() {
 *         return value;
 *     }
 * 
 *     public void setValue(final String value) {
 *         this.value = value;
 *     }
 * 
 *     public String toString() {
 *         return value;
 *     }
 * }
 * </pre>
 * 
 * If the implementation of this class is lazy (the method {@link #isLazy()}
 * returns true), an instance of this implementation registers itself in the
 * NlsContext only on the first invocation of the {@link #getBundle()} method.
 * When the locale is changed, the reference to the current bundle is
 * invalidated and will be updated only on the next invocation of the method
 * {@link #getBundle()}.<br/>
 * Most probably, lazy implementations will implement the methods
 * {@link #getBundle()} and
 * {@link #handleLocaleChange(NlsContext, Locale, Locale, Object)
 * handleLocaleChange(NlsContext, Locale, Locale, Object)} in the similar way:
 * 
 * <pre>
 * public NlsBundle&lt;K, V&gt; getBundle() {
 *     if (ref == null) {
 *         final NlsContext ctx = getNlsContext();
 * 
 *         if (!registered) {
 *             ctx.addLocaleChangeListener(this, handback);
 *             registered = true;
 *         }
 * 
 *         ref = bundleStorage.getBundle(ctx.getLocale());
 *     }
 * 
 *     return ref;
 * }
 * 
 * public void handleLocaleChange(final NlsContext context,
 *         final Locale oldLocale, final Locale newLocale, final Object handback) {
 *     ref = null;
 * }
 * </pre>
 * 
 * Lazy implementations are most suitable for applications, in which locale is
 * changing often. <br/>
 * Lazy implementation usage example:
 * 
 * <pre>
 * public enum Msg implements NlsKey&lt;String&gt; {
 *     Hello(&quot;Hello&quot;),
 *     Bye(&quot;Bye&quot;);
 * 
 *     private static final CurrentBundle&lt;Msg, String&gt; currentBundle = CurrentBundle
 *             .newInstance(
 *                 NlsContext.getDefault(),
 *                 new BundleStorage&lt;Msg, String&gt;(Msg.class, EnumBundleLoader
 *                         .&lt;Msg, String&gt; getDefault(false)), true);
 * 
 *     private final String defaultValue;
 * 
 *     private Msg(final String defaultValue) {
 *         this.defaultValue = defaultValue;
 *     }
 * 
 *     public static CurrentBundle&lt;Msg, String&gt; getCurrentBundle() {
 *         return currentBundle;
 *     }
 * 
 *     public String getName() {
 *         return name();
 *     }
 * 
 *     public String getDefaultValue(final Locale locale) {
 *         return defaultValue;
 *     }
 * 
 *     public String toString() {
 *         return currentBundle.get(this);
 *     }
 * }
 * </pre>
 * 
 * @author Andrey Pavlenko
 */
public abstract class CurrentBundle<K extends NlsKey<V>, V> implements
        NlsBundle<K, V>, LocaleChangeListener {

    /**
     * Create a new instance of CurrentBundle with the specified arguments.
     */
    public static <K extends NlsKey<V>, V> CurrentBundle<K, V> newInstance(
            final NlsContext nlsContext,
            final BundleStorage<K, V> bundleStorage, final boolean lazy) {
        return lazy ? new LazyBundleImpl<K, V>(nlsContext, bundleStorage)
                : new NonLazyBundleImpl<K, V>(nlsContext, bundleStorage);
    }

    /**
     * Returns the {@link NlsContext} listening by this instance.
     */
    public abstract NlsContext getNlsContext();

    /**
     * Returns the bundle storage used by this instance to retrieve/load
     * bundles.
     */
    public abstract BundleStorage<K, V> getBundleStorage();

    /**
     * Returns the current bundle (i.e. the bundle corresponding to
     * {@link #getNlsContext()}.{@link NlsContext#getLocale() getLocale()}
     * locale).
     */
    public abstract NlsBundle<K, V> getBundle();

    /**
     * Returns true if this instance is lazy, otherwise - returns false.
     */
    public abstract boolean isLazy();

    /**
     * {@inheritDoc} <br/>
     * The default implementation of this method always returns
     * {@link #getBundle()}.{@link NlsBundle#get(NlsKey) get(key)}.
     */
    @Override
    public V get(final K key) {
        return getBundle().get(key);
    }

    /**
     * {@inheritDoc} <br/>
     * The default implementation of this method always returns
     * {@link #getBundle()}.{@link NlsBundle#contains(NlsKey) contains(key)}.
     */
    @Override
    public boolean contains(final K key) {
        return getBundle().contains(key);
    }

    /**
     * {@inheritDoc} <br/>
     * The default implementation of this method always returns
     * {@link #getNlsContext()}.{@link NlsContext#getLocale() getLocale()}.
     */
    @Override
    public Locale getLocale() {
        return getNlsContext().getLocale();
    }

    private abstract static class AbstractCurrentBundle<K extends NlsKey<V>, V>
            extends CurrentBundle<K, V> {
        final Object handback = new Object();
        final NlsContext nlsContext;
        final BundleStorage<K, V> bundleStorage;

        AbstractCurrentBundle(final NlsContext nlsContext,
                final BundleStorage<K, V> bundleStorage) {
            this.nlsContext = nlsContext;
            this.bundleStorage = bundleStorage;
        }

        @Override
        public final NlsContext getNlsContext() {
            return nlsContext;
        }

        @Override
        public final BundleStorage<K, V> getBundleStorage() {
            return bundleStorage;
        }
    }

    private static final class LazyBundleImpl<K extends NlsKey<V>, V> extends
            AbstractCurrentBundle<K, V> {
        private final AtomicReference<NlsBundle<K, V>> ref = new AtomicReference<NlsBundle<K, V>>();
        private final AtomicBoolean registered = new AtomicBoolean();

        LazyBundleImpl(final NlsContext nlsContext,
                final BundleStorage<K, V> bundleStorage) {
            super(nlsContext, bundleStorage);
        }

        @Override
        public boolean isLazy() {
            return true;
        }

        @Override
        public NlsBundle<K, V> getBundle() {
            NlsBundle<K, V> b;

            for (;;) {
                b = ref.get();

                if (b != null) {
                    return b;
                }
                if (!registered.get() && registered.compareAndSet(false, true)) {
                    nlsContext.addLocaleChangeListener(this, handback);
                }

                b = bundleStorage.getBundle(nlsContext.getLocale());

                if (ref.compareAndSet(null, b)) {
                    return b;
                }
            }
        }

        @Override
        public void handleLocaleChange(final NlsContext context,
                final Locale oldLocale, final Locale newLocale,
                final Object handback) {
            if (handback == this.handback) {
                ref.set(null);
            }
        }
    }

    static final class NonLazyBundleImpl<K extends NlsKey<V>, V> extends
            AbstractCurrentBundle<K, V> {
        private final AtomicReference<NlsBundle<K, V>> ref = new AtomicReference<NlsBundle<K, V>>();

        NonLazyBundleImpl(final NlsContext nlsContext,
                final BundleStorage<K, V> bundleStorage) {
            super(nlsContext, bundleStorage);
            nlsContext.addLocaleChangeListener(this, handback);
            ref.compareAndSet(null,
                bundleStorage.getBundle(nlsContext.getLocale()));
        }

        @Override
        public boolean isLazy() {
            return false;
        }

        @Override
        public NlsBundle<K, V> getBundle() {
            return ref.get();
        }

        @Override
        public void handleLocaleChange(final NlsContext context,
                final Locale oldLocale, final Locale newLocale,
                final Object handback) {
            if (handback == this.handback) {
                ref.set(bundleStorage.getBundle(newLocale));
            }
        }
    }
}
